package com.xci.demo;

import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.digest.SM3;
import cn.hutool.crypto.symmetric.SM4;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.SecureRandom;

public class SmCryptoUtils {
    public record SM2KeyPair(String privateKey, String publicKey,String privateKeyD, String publicKeyQ) {}

    public static final String ALGORITHM_SM2 = "SM2";

    /**
     * sm3哈希算法
     * @param plainText 需要计算的明文
     * @return 哈希值
     */
    public static String sm3Hash(String plainText) {
        return new SM3().digestHex(plainText);
    }

    /**
     * 生成sm4密钥
     * @param len 长度,默认位32个字符,128位
     * @return 生成的密钥
     */
    public static String sm4GenerateKey(int len) {
        SecureRandom random = new SecureRandom();
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < len; i++) {
            buffer.append(Integer.toHexString(random.nextInt(16)));
        }
        return buffer.toString();
    }

    // 重载方法，使用默认长度32
    public static String sm4GenerateKey() {
        return sm4GenerateKey(32);
    }

    /**
     * sm4加密,cbc模式,使用 pkcs#7 填充
     * @param plainText 明文
     * @param key       密钥,16进制串，要求为128位
     * @param iv        向量,16进制字符串，要求为128位
     * @return 加密后的密文
     */
    public static String sm4Encrypt(String plainText, String key, String iv) {
        SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, HexUtil.decodeHex(key), HexUtil.decodeHex(iv));
        return sm4.encryptHex(plainText);
    }

    /**
     * sm4解密,cbc模式,使用 pkcs#7 填充
     * @param cipherText 密文,16进制串
     * @param key        密钥,16进制串，要求为128位
     * @param iv         向量,16进制字符串，要求为128位
     * @return 解密后的明文
     */
    public static String sm4Decrypt(String cipherText, String key, String iv) {
        SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, HexUtil.decodeHex(key), HexUtil.decodeHex(iv));
        return sm4.decryptStr(cipherText);
    }

    /**
     * 生成sm2私钥和公钥对
     * @return 密钥对
     */
    public static SM2KeyPair sm2GenerateKeyPairHex() {
        KeyPair pair = KeyUtil.generateKeyPair(ALGORITHM_SM2);
        byte[] privateKey = pair.getPrivate().getEncoded();
        byte[] publicKey = pair.getPublic().getEncoded();
        // 生成私钥 D，以D值做为js端的解密私钥
        byte[] privateKeyD = BCUtil.encodeECPrivateKey(pair.getPrivate());
        // 生成公钥 Q，以Q值做为js端的加密公钥
        byte[] publicKeyQ = ((BCECPublicKey) pair.getPublic()).getQ().getEncoded(false);
        return new SM2KeyPair(HexUtil.encodeHexStr(privateKey), HexUtil.encodeHexStr(publicKey),
                HexUtil.encodeHexStr(privateKeyD), HexUtil.encodeHexStr(publicKeyQ));
    }

    // /**
    //  * 生成压缩公钥
    //  * @param publicKey 公钥,16进制字符串
    //  * @return 压缩后的公钥
    //  */
    // public static String sm2CompressPublicKeyHex(String publicKey) {
    //     SM2 sm2 = new SM2();
    //     return sm2.compressPublicKeyHex(publicKey);
    // }

    // /**
    //  * 验证公钥是否为椭圆曲线上的点
    //  * @param publicKey 公钥,16进制字符串
    //  * @return 验证结果
    //  */
    // public static boolean sm2VerifyPublicKey(String publicKey) {
    //     SM2 sm2 = new SM2();
    //     return sm2.verifyPublicKey(publicKey);
    // }

    // /**
    //  * 验证公钥是否等价，等价返回true
    //  * @param publicKey1 公钥1,16进制字符串
    //  * @param publicKey2 公钥2,16进制字符串
    //  * @return 比较结果
    //  */
    // public static boolean sm2ComparePublicKeyHex(String publicKey1, String publicKey2) {
    //     SM2 sm2 = new SM2();
    //     return sm2.comparePublicKey(publicKey1, publicKey2);
    // }

    /**
     * sm2加密
     * @param plainText  明文
     * @param publicKey  公钥,16进制字符串
     * @param cipherMode 加密模式: 1 - C1C3C2，0 - C1C2C3，默认为1
     * @return 加密后的密文
     */
    public static String sm2Encrypt(String plainText, String publicKey, SM2Engine.Mode cipherMode) {
        SM2 sm2 = new SM2(null, publicKey);
        if (cipherMode == SM2Engine.Mode.C1C2C3) sm2.setMode(cipherMode);
        return remove04Prefix(sm2.encryptHex(plainText, KeyType.PublicKey));
    }

    /**
     * sm2加密
     * @param plainText  明文
     * @param publicKey  公钥,16进制字符串
     * @return 加密后的密文
     */
    public static String sm2Encrypt(String plainText, String publicKey) {
        return sm2Encrypt(plainText, publicKey, SM2Engine.Mode.C1C3C2);
    }

    /**
     * sm2解密
     * @param cipherText 密文,16进制字符串
     * @param privateKey 私钥,16进制字符串
     * @param cipherMode 加密模式: 1 - C1C3C2，0 - C1C2C3，默认为1
     * @return 解密后的明文
     */
    public static String sm2Decrypt(String cipherText, String privateKey, SM2Engine.Mode cipherMode) {
        SM2 sm2 = new SM2(privateKey, null);
        if (cipherMode == SM2Engine.Mode.C1C2C3) sm2.setMode(cipherMode);
        return sm2.decryptStr(add04PrefixIfNeeded(cipherText) , KeyType.PrivateKey);
    }

    /**
     * sm2解密
     * @param cipherText 密文,16进制字符串
     * @param privateKey 私钥,16进制字符串
     * @return 解密后的明文
     */
    public static String sm2Decrypt(String cipherText, String privateKey) {
        return sm2Decrypt(cipherText, privateKey, SM2Engine.Mode.C1C3C2);
    }

    /**
     * 签名
     * @param plainText  明文
     * @param privateKey 私钥,16进制字符串
     * @return 签名值
     */
    public static String sm2Signature(String plainText, String privateKey) {
        SM2 sm2 = new SM2(privateKey, null);
        return HexUtil.encodeHexStr(sm2.sign(StrUtil.bytes(plainText, StandardCharsets.UTF_8)));
    }

    /**
     * 验签
     * @param plainText 明文
     * @param signHex   签名值,16进制字符串
     * @param publicKey 公钥,16进制字符串
     * @return 验签结果
     */
    public static boolean sm2VerifySignature(String plainText, String signHex, String publicKey) {
        SM2 sm2 = new SM2(null, publicKey);
        return sm2.verify(StrUtil.bytes(plainText, StandardCharsets.UTF_8), HexUtil.decodeHex(signHex));
    }

    private static String remove04Prefix(String hex) {
        return hex.startsWith("04") ? hex.substring(2) : hex;
    }

    private static String add04PrefixIfNeeded(String hex) {
        return hex.startsWith("04") ? hex : "04" + hex;
    }
}
